
import numpy as np
import random
import math as mt
class Linear:
    def __init__(self,w,b,size):
        self.m=size
        self.alpha=0.001 #学习率
        self.beta1=0.9
        self.beta1t=1 #beta1的t次方
        self.beta2=0.999
        self.beta2t=1
        self.mt=np.zeros(2)
        self.vt=np.zeros(2)
        self.eps=1e-8
        self.x_data=[]
        self.x_mean=0 #x_data的均值
        self.x_std=0 #x_data的标准差
        self.y_data=[]
        self.y_mean=0
        self.y_std=0
        self.w=np.zeros(2)
        self.data_get(w,b)

    def data_get(self,w,b):
        x=[random.uniform(-100.0,100.0) for _ in range(self.m)] #随机生成给定数量的[-100,100)数据
        y=np.multiply(x,w)+b+np.random.uniform(-1,1,self.m) #按照给定的w和b生成y值并增加一定的噪音
        self.x_mean=np.mean(x)
        self.x_std=np.std(x)
        self.y_mean=np.mean(y)
        self.y_std=np.std(y)
        self.x_data=[[(x[i]-self.x_mean)/self.x_std,1]for i in range(self.m)] #数据归一化
        self.y_data=(y-self.y_mean)/self.y_std

    def w_to_w(self):
        gradient=np.dot(np.dot(self.x_data,self.w)-self.y_data,self.x_data)/self.m #生成梯度
        #mt是梯度的滑动平均，梯度在指向最优解的方向上叠加，在垂直指向最优解方向的方向上抵消
        self.mt=self.mt*self.beta1+(1-self.beta1)*gradient
        #vt是梯度的变化率的平均，当梯度变化过快时，对应学习率应减小
        self.vt=self.vt*self.beta2+(1-self.beta2)*np.multiply(gradient,gradient)
        self.beta1t=self.beta1t*self.beta1
        self.beta2t=self.beta2t*self.beta2
        mt_=self.mt/(1-self.beta1t)
        vt_=self.vt/(1-self.beta2t)
        self.w=self.w-self.alpha*mt_/(np.sqrt(vt_)+self.eps)
        return 1.0/(2*self.m)*np.sum(np.square(np.dot(self.x_data, self.w)-self.y_data))

    def linear_regression(self):
        time = 0
        last_cost=0
        while (True):
            cost = self.w_to_w()
            time = time + 1
            print("第{}次梯度下降损失为: {}".format(time, round(cost, 6)))
            if mt.fabs(cost-last_cost) < 1e-10:
                print(self.w)
                return
            else: last_cost=cost

    #根据线性回归得到的模型预测-100到100数据对应的y值
    def predict(self,x):
        x=(x-self.x_mean)/self.x_std
        res=np.dot([x,1],self.w)
        return res*self.y_std+self.y_mean
w=float(input('输入x的系数w：'))
b=float(input('输入对应常数b：'))
size=int(input('输入数据数量：'))
lnreg=Linear(w,b,size)
lnreg.linear_regression()
num=float(input('输入一个x值:(-100<=x<100)'))
print(lnreg.predict(num))
